using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;

namespace Dragoon
{
    public class Geometry
    {
        static public readonly Vector3 Up = new Vector3(0, -1, 0);
        static public readonly Vector3 Left = new Vector3(-1, 0, 0);
        static public readonly Vector3 Down = Up * -1;
        static public readonly Vector3 Right = Left * -1;
        static public float MaxY = 0;
        static public float MaxX = 0;
        static public Rectangle CreateRectangle(Vector3 topLeft, Vector3 bottomRight)
        {
            Rectangle result = new Rectangle();
            result.X = (int) topLeft.X;
            result.Y = (int) topLeft.Y;
            result.Width = (int) (bottomRight.X - result.X);
            result.Height =(int) (bottomRight.Y - result.Y);
            return result;
        }
        static public Vector3 ConvertToVector(Point p)
        {
            return new Vector3(p.X, p.Y, 0);
        }
        
        static public Point ConvertToPoint(Vector3 p)
        {
            return new Point((int) p.X, (int) p.Y);
        }

        static public Vector3 ConvertCenterToVector(Rectangle r)
        {
            return ConvertToVector(new Point(r.X + r.Width/2,r.Y + r.Height/2));
        }
    }

    public enum Side { 
        Left, Right, 
        Top, Bottom,
        CnrLeftTop, CnrRightTop, 
        CnrLeftBottom, CnrRightBottom};


    public interface ICollidable
    {
        BoundingBox Bounds { get;}
    }

    abstract public class Bounded2D : ICollidable
    {
        protected BoundingBox _boundingBox;
        public BoundingBox Bounds { get { return _boundingBox; } }
        protected Vector3 MakeVector3(Point p, float z)
        {
            return new Vector3(p.X, p.Y, z);
        }


    }

    public class Movable : Bounded2D
    {
        /// <summary>
        /// The origen for the movement vector
        /// </summary>
        private Vector3 _origen;
        /// <summary>
        /// The speed (in units per second)
        /// </summary>
        private float _speed;
        /// <summary>
        /// The direction for the move
        /// </summary>
        private Vector3 _direction;
        /// <summary>
        /// The start time for moving.  If less than zero, object is not moving.
        /// </summary>
        private double _startTime = -1.0;
        /// <summary>
        /// Only a visible object can partake in collisions
        /// </summary>
        private bool _isVisible = false;
        private Vector3 _dimensionHalf;
        private Rectangle _position2D;

        protected Movable(Vector3 dimensions, Vector3 origen, float speed)
        {
            _startTime = 0;
            _origen = origen;
            _speed = speed;
            SetDimensions(dimensions);
            SetBoundingBox(origen);
        }

        public Rectangle Position
        {
            get
            {
                return _position2D;
            }
        }

        private void SetBoundingBox(Vector3 position)
        {
            Vector3 lt = position - _dimensionHalf;
            Vector3 rb = position + _dimensionHalf;
            _boundingBox = new BoundingBox(lt,rb);
            _position2D = Geometry.CreateRectangle(lt, rb);
        }

        protected void SetDimensions(Vector3 dimensions)
        {
            _dimensionHalf = dimensions * 0.5F;
            SetBoundingBox(Geometry.ConvertCenterToVector(Position));
        }

        public bool IsMoving
        {
            get
            {
                return _startTime > 0;
            }
        }

        public bool IsVisible
        {
            get
            {
                return _isVisible;
            }
        }

        protected void Show()
        {
            _isVisible = true;
        }

        protected void Hide()
        {
            _isVisible = false;
        }

        /// <summary>
        /// Start moving in the given direction
        /// </summary>
        /// <param name="time">the current time</param>
        /// <param name="direction">no need to normalise</param>
        protected void StartMoving(GameTime time, Vector3 direction)
        {
            _startTime = time.TotalGameTime.TotalSeconds;
            _direction = direction;
            _direction.Normalize();
        }

        protected void StopMoving(GameTime time)
        {
            SetOrigin(LocationAt(time));
        }


        /// <summary>
        /// Relocates <see cref="Movable"/> to new origin and stops it
        /// </summary>
        /// <param name="value"></param>
        protected void SetOrigin(Vector3 value)
        {
            _origen = value;
            _startTime = -1.0;
            _direction = Vector3.Zero;
            SetBoundingBox(_origen);
        }        

        virtual protected Vector3 LocationAt(GameTime time)
        {
            float distanceTravelled = _speed *
                (float)(time.TotalGameTime.TotalSeconds - _startTime);
            return _origen + (_direction * distanceTravelled);
        }

        public void Update(GameTime time)
        {
            if (!IsMoving)
                return;
            SetBoundingBox(LocationAt(time));
        }
    }

    class CollisionSet
    {
        /// <summary>
        /// Add <see cref="ICollidable"/>s to this List
        /// </summary>
        public readonly List<ICollidable> Items =
            new List<ICollidable>();

        /// <summary>
        /// Goes through all the <see cref="Items"/> to find the first item hit by the <see cref="Ray"/>
        /// </summary>
        /// <param name="ray">the <see cref="Ray"/> to check</param>
        /// <param name="objectHit">the <see cref="ICollidable"/> that was hit, null if nothing was hit</param>
        /// <param name="distance">the distance to the object's boundary, <see cref="float.MaxValue"/> if nothing was hit</param>
        /// <returns>false if no object was hit</returns>
        public bool Intersects(Ray ray, 
            out ICollidable objectHit, 
            out float distance)
        {
            distance = float.MaxValue;
            objectHit = null;
            float? itemDistance;
            foreach (ICollidable o in Items)
            {
                itemDistance = o.Bounds.Intersects(ray);
                if ((itemDistance ?? float.MaxValue) < distance)
                {
                    distance = itemDistance.Value;
                    objectHit = o;
                }
            }
            return objectHit != null;
        }

        public bool Intersects(BoundingSphere sphere, out ICollidable objHit,            
            out Side sideHit)
        {
            float firstHit = float.MaxValue;
            objHit = null;
            foreach (ICollidable o in Items)
            {
                if (o.Bounds.Intersects(sphere))
                {
                    Vector3 center = o.Bounds.Min + (o.Bounds.Max - o.Bounds.Min) / 2.0F;
                    float dist = (sphere.Center - center).LengthSquared();
                    if (dist < firstHit)
                    {
                        firstHit = dist;
                        objHit = o;
                    }
                }
            }

            if (objHit != null)
            {
                // determine which Side was hit
                Ray r = new Ray(sphere.Center, Geometry.Up);
                float? dist;
                dist = objHit.Bounds.Intersects(r);
                if (dist.HasValue)
                    sideHit = Side.Top;
                else
                {
                    r.Direction = Geometry.Down;
                    dist = objHit.Bounds.Intersects(r);
                    if (dist.HasValue)
                        sideHit = Side.Top;
                    else
                    {
                        r.Direction = Geometry.Left;
                        dist = objHit.Bounds.Intersects(r);
                        if (dist.HasValue)
                            sideHit = Side.Right;
                        else
                        {
                            r.Direction = Geometry.Right;
                            dist = objHit.Bounds.Intersects(r);
                            if (dist.HasValue)
                                sideHit = Side.Left;
                            else
                            {
                                // a corner was hit
                                bool left = r.Position.X < objHit.Bounds.Min.X;
                                bool top = r.Position.Y < objHit.Bounds.Min.Y;
                                if (left)
                                    if (top)
                                        sideHit = Side.CnrLeftTop;
                                    else
                                        sideHit = Side.CnrLeftBottom;
                                else
                                    if (top)
                                        sideHit = Side.CnrRightTop;
                                    else
                                        sideHit = Side.CnrRightBottom;

                            }
                        }
                    }
                }
                return true;
            }
            else
            {
                sideHit = Side.Left;
                return false;
            }
        }

        /// <summary>
        /// Finds the first element in a given list that collides with a containing
        /// </summary>
        /// <param name="movables"></param>
        /// <param name="movableHit"></param>
        /// <param name="collidableHit"></param>
        /// <returns></returns>
        public bool FindFirstHit<TMovable>(
            List<TMovable> movables, 
            out TMovable movableHit, 
            out ICollidable collidableHit)
            where TMovable : class, ICollidable
        {
            /// Find a collision
            bool found = false;
            int itemIndex = 0;
            int movableIndex = 0;
            movableHit = null;
            collidableHit = null;
            while (!found && movableIndex < movables.Count)
            {
                movableHit = movables[movableIndex++];
                itemIndex = 0;
                while (!found && itemIndex < Items.Count)
                {
                    collidableHit = Items[itemIndex++];
                    if (collidableHit.Bounds.Intersects(movableHit.Bounds))
                        found = true;
                }
            }
            return found;
        }

    }
}
